package org.arclib.x.light;

import java.util.ArrayList;

import org.arclib.math.Point;

public class Shadow
{

	/**
	 * scales a vector with respect to the light radius used for penumbra and
	 * umbra lights where the tips are not supposed to be visible
	 **/
	static Point extendDir(Point dir, Light light)
	{
		return dir.normalizeCopy().mult(light.getOuterRadius()).mult(1.5);
	}

	static Point getLightDisplacement(Point reference, Light light,
			LightBlocker blocker)
	{
		Point lightdisp = Point.makePerpTo(reference.sub(light.getPosition()));
		lightdisp.normalize();
		lightdisp.multEquals(light.sourceradius);
		if (lightdisp.dot(reference.sub(blocker.position)) < 0.)
			lightdisp.multEquals(-1.);
		return lightdisp;
	}

	/**
	 * Gets the direction that marks the beginning of total shadow for the given
	 * point.
	 **/
	static Point getTotalShadowStartDirection(Point at, Light light,
			LightBlocker blocker)
	{
		return extendDir(at.sub(light.getPosition().add(
				getLightDisplacement(at, light, blocker))), light);
	}

	/***
	 * Draws the shadow of 'blocker' under the given 'light'
	 */
	public static void renderShadow(Light light, LightBlocker blocker)
	{
		// get the line that blocks light for the blocker and light combination
		// move the light position towards blocker by its source radius to avoid
		// popping of penumbrae
		Point normal = blocker.position.sub(light.getPosition());
		normal.normalize();

		ArrayList<Point> blockerLine = blocker.getBlockedLine(light
				.getPosition().add(normal.mult(light.sourceradius)));

		// if the light source is completely surrounded by the blocker, don't
		// draw its shadow
		if (blockerLine.size() == blocker.shape.edges.size() + 1)
			return;

		/**
		 * Displaces the light pos by sourceradius orthogonal to the line from
		 * reference to the light's position. Used for calculating penumbra
		 * size.
		 **/

		//
		// build penumbrae (soft shadows), cast from the edges
		//

		Penumbra rightpenumbra = new Penumbra();

		Point startdir = extendDir(blockerLine.get(0).sub(
				light.getPosition()
						.sub(
								getLightDisplacement(blockerLine.get(0), light,
										blocker))), light);
		rightpenumbra.sections.add(new Section(blockerLine.get(0), startdir,
				0.0));
		for (int i = 0; i < blockerLine.size() - 1; ++i)
		{
			double wanted = Math.abs(startdir
					.angle(getTotalShadowStartDirection(blockerLine.get(i),
							light, blocker)));
			double available = Math.abs(startdir.angle(blockerLine.get(i + 1)
					.sub(blockerLine.get(i))));

			if (wanted < available)
			{
				rightpenumbra.sections.add(new Section(blockerLine.get(i),
						getTotalShadowStartDirection(blockerLine.get(i), light,
								blocker), 1.0));
				break;
			} else
			{
				rightpenumbra.sections.add(new Section(blockerLine.get(i + 1),
						extendDir(blockerLine.get(i + 1)
								.sub(blockerLine.get(i)), light), available
								/ wanted));
			}
		}

		Penumbra leftpenumbra = new Penumbra();

		Point startdir1 = extendDir(blockerLine.get(blockerLine.size() - 1)
				.sub(
						light.getPosition().sub(
								getLightDisplacement(blockerLine
										.get(blockerLine.size() - 1), light,
										blocker))), light);
		leftpenumbra.sections.add(new Section(blockerLine.get(blockerLine
				.size() - 1), startdir1, 0.0));

		for (int i = 0; i < blockerLine.size() - 1; ++i)
		{
			double wanted = Math.abs(startdir1
					.angle(getTotalShadowStartDirection(blockerLine
							.get(blockerLine.size() - i - 1), light, blocker)));
			double available = Math.abs(startdir1.angle(blockerLine.get(
					blockerLine.size() - i - 2).sub(
					blockerLine.get(blockerLine.size() - i - 1))));

			if (wanted < available)
			{
				leftpenumbra.sections.add(new Section(blockerLine
						.get(blockerLine.size() - i - 1),
						getTotalShadowStartDirection(blockerLine
								.get(blockerLine.size() - i - 1), light,
								blocker), 1.0));
				break;
			} else
			{
				leftpenumbra.sections.add(new Section(blockerLine
						.get(blockerLine.size() - i - 2), extendDir(blockerLine
						.get(blockerLine.size() - i - 2).sub(
								blockerLine.get(blockerLine.size() - i - 1)),
						light), available / wanted));
			}
		}

		//
		// build umbrae (hard shadows), cast between the insides of penumbrae
		//

		Umbra umbra = new Umbra();

		int rsize = rightpenumbra.sections.size();
		int lsize = leftpenumbra.sections.size();

		umbra.sections.add(new Section(
				rightpenumbra.sections.get(rsize - 1).base,
				rightpenumbra.sections.get(rsize - 1).direction));

		for (int i = rsize - 1; i < lsize - lsize + 1; i++)
		{
			umbra.sections.add(new Section(blockerLine.get(i), extendDir(
					leftpenumbra.sections.get(lsize - 1).direction.add(
							rightpenumbra.sections.get(rsize - 1).direction)
							.mult(.5), light)));
		}

		umbra.sections.add(new Section(
				leftpenumbra.sections.get(lsize - 1).base,
				leftpenumbra.sections.get(lsize - 1).direction));

		//
		// draw shadows to alpha
		//

		umbra.draw();
		rightpenumbra.draw();
		leftpenumbra.draw();
	}

}
